package com.alipan.service.impl;

import com.alibaba.fastjson.JSON;
import com.alipan.constant.Constants;
import com.alipan.request.*;
import com.alipan.response.*;
import com.alipan.service.FileService;
import com.alipan.service.HttpClient;
import com.alipan.utils.TimeUtils;
import io.vavr.Tuple4;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;


@Slf4j
@Service
public class FileServiceImpl implements FileService {

    private static int chunkSize = 1024 * 1024 * 10;//10MB

    private HttpClient httpClient;

    @Autowired
    public void setHttpClient(HttpClient httpClient) {
        this.httpClient = httpClient;
    }

    @Override
    public ListFileResponse fileList(Map<String, String> headers, ListFileRequest request) {
        String res = httpClient.post(headers, Constants.OPEN_FILE_LIST, request);
        return JSON.parseObject(res, ListFileResponse.class);
    }

    @Override
    public SearchResponse searchFile(Map<String, String> headers, SearchRequest request) {
        String res = httpClient.post(headers, Constants.OPEN_FILE_SEARCH, request);
        return JSON.parseObject(res, SearchResponse.class);
    }

    @Override
    public StarredResponse starredFileList(Map<String, String> headers, StarredRequest request) {
        String res = httpClient.post(headers, Constants.OPEN_FILE_STARRED_LIST, request);
        return JSON.parseObject(res, StarredResponse.class);
    }

    @Override
    public GetFileResponse fileInfo(Map<String, String> headers, GetFileRequest request) {
        String res = httpClient.post(headers, Constants.OPEN_FILE_GET, request);
        return JSON.parseObject(res, GetFileResponse.class);
    }

    @Override
    public List<GetFileResponse> bathFileInfo(Map<String, String> headers, BathGetRequest request) {
        String res = httpClient.post(headers, Constants.OPEN_FILE_BATCH_GET, request);
        return JSON.parseArray(res, GetFileResponse.class);
    }

    @Override
    public List<GetFileResponse> searchFileByPath(Map<String, String> headers, PathGetFileRequest request) {
        String res = httpClient.post(headers, Constants.OPEN_FILE_GET_BY_PATH, request);
        return JSON.parseArray(res, GetFileResponse.class);
    }

    @Override
    public DownloadResponse downloadFile(Map<String, String> headers, DownloadRequest request) {
        String res = httpClient.post(headers, Constants.OPEN_FILE_GET_DOWNLOAD_URL, request);
        return JSON.parseObject(res, DownloadResponse.class);
    }

    @Override
    public CreateResponse createFile(Map<String, String> headers, CreateRequest request) {
        Long size = request.getSize() == null ? 0 : request.getSize();
        int chunkCount = (int) Math.ceil(Double.valueOf(size) / Double.valueOf(chunkSize));// 进1法
        List<CreateRequest.PartInfo> partInfoList = new ArrayList();
        for (int i = 1; i <= chunkCount; i++) {
            CreateRequest.PartInfo partInfo = new CreateRequest.PartInfo();
            partInfo.setPartNumber(i);
            partInfoList.add(partInfo);
        }
        request.setPartInfoList(partInfoList);
        String res = httpClient.post(headers, Constants.OPEN_FILE_CREATE, request);
        return JSON.parseObject(res, CreateResponse.class);
    }

    @Override
    public RefreshResponse refreshUploadUrl(Map<String, String> headers, RefreshRequest request) {
        String res = httpClient.post(headers, Constants.OPEN_FILE_UPLOAD_URL, request);
        return JSON.parseObject(res, RefreshResponse.class);
    }

    @Override
    public ListPartResponse listPart(Map<String, String> headers, ListPartRequest request) {
        String res = httpClient.post(headers, Constants.OPEN_FILE_LIST_PART, request);
        return JSON.parseObject(res, ListPartResponse.class);
    }

    @Override
    public CompleteResponse completeFile(Map<String, String> headers, CompleteRequest request) {
        String res = httpClient.post(headers, Constants.OPEN_FILE_COMPLETE, request);
        return JSON.parseObject(res, CompleteResponse.class);
    }

    @Override
    @SneakyThrows
    public CompleteResponse uploadFile(Map<String, String> headers, CreateResponse createResponse, InputStream inputStream, long length) {
        if (length > 0) {
            List<PartInfo> partInfoList = createResponse.getPartInfoList();
            Map<Integer, PartInfo> partMap = new HashMap<>();
            partInfoList.forEach(part -> partMap.put(part.getPartNumber(), part));
            log.debug("共:{}块需要上传", partInfoList.size());
            StopWatch watch = new StopWatch();
            watch.start("上传耗时");
            LinkedBlockingQueue<Tuple4<String, byte[], Integer, Integer>> queue = new LinkedBlockingQueue(2);
            AtomicBoolean finish = new AtomicBoolean(false);
            CompletableFuture<Void> supplyAsync = CompletableFuture.runAsync(() -> {
                while (true) {
                    if (queue.size() == 0 && finish.get()) break;
                    Tuple4<String, byte[], Integer, Integer> tuple4;
                    try {
                        tuple4 = queue.poll(10, TimeUnit.SECONDS);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    if (tuple4 == null) continue;
                    httpClient.upload(headers, tuple4._1, tuple4._2, tuple4._3);
                    log.debug("第:{}块上传完毕...", tuple4._4);
                }
            });
            for (Integer i = 1; i <= partInfoList.size(); i++) {
                PartInfo partInfo = partMap.get(i);
                int number = partInfo.getPartNumber();
                String uploadUrl = partInfo.getUploadUrl();
                boolean expires = TimeUtils.expires(uploadUrl);
                if (expires) {
                    List<RefreshRequest.PartInfo> parts = new ArrayList<>();
                    for (int j = i; j < partInfoList.size(); j++) {
                        parts.add(new RefreshRequest.PartInfo(j));
                    }
                    RefreshRequest request = RefreshRequest.builder().driveId(createResponse.getDriveId()).fileId(createResponse.getFileId()).uploadId(createResponse.getUploadId()).partInfoList(parts).build();
                    RefreshResponse response = this.refreshUploadUrl(headers, request);
                    List<PartInfo> infoList = response.getPartInfoList();
                    infoList.forEach(part -> partMap.put(part.getPartNumber(), part));
                }
                byte[] bytes = new byte[chunkSize];
                int read = IOUtils.read(inputStream, bytes);
                Tuple4<String, byte[], Integer, Integer> tuple4 = new Tuple4<>(uploadUrl, bytes, read, number);
                queue.put(tuple4);
                log.debug("第:{}块入队完毕...", number);
            }
            finish.set(true);
            watch.stop();
            CompletableFuture.allOf(supplyAsync).join();
            log.debug("上传耗时:{}秒", watch.getTotalTimeMillis() / 1000.0);
        }
        CompleteRequest completeRequest = new CompleteRequest();
        completeRequest.setDriveId(createResponse.getDriveId());
        completeRequest.setFileId(createResponse.getFileId());
        completeRequest.setUploadId(createResponse.getUploadId());
        CompleteResponse completeResponse = this.completeFile(headers, completeRequest);
        DownloadRequest downloadRequest = DownloadRequest.builder().driveId(completeResponse.getDriveId()).fileId(completeResponse.getFileId()).build();
        DownloadResponse downloadResponse = this.downloadFile(headers, downloadRequest);
        completeResponse.setDownloadUrl(downloadResponse.getUrl());
        log.debug("上传完成...");
        return completeResponse;
    }

    @Override
    @SneakyThrows
    public InputStream download(Map<String, String> headers, String url, Consumer<Map> consumer) {
        return httpClient.download(headers, url, consumer);
    }


    @Override
    public RenameResponse renameFile(Map<String, String> headers, RenameRequest request) {
        String res = httpClient.post(headers, Constants.OPEN_FILE_UPDATE, request);
        return JSON.parseObject(res, RenameResponse.class);
    }

    @Override
    public MoveResponse moveFile(Map<String, String> headers, MoveRequest request) {
        String res = httpClient.post(headers, Constants.OPEN_FILE_MOVE, request);
        return JSON.parseObject(res, MoveResponse.class);
    }

    @Override
    public CopyResponse copyFile(Map<String, String> headers, CopyRequest request) {
        String res = httpClient.post(headers, Constants.OPEN_FILE_COPY, request);
        return JSON.parseObject(res, CopyResponse.class);
    }

    @Override
    public TrashResponse trashFile(Map<String, String> headers, TrashRequest request) {
        String res = httpClient.post(headers, Constants.OPEN_FILE_TRASH, request);
        return JSON.parseObject(res, TrashResponse.class);
    }

    @Override
    public DeleteResponse deleteFile(Map<String, String> headers, DeleteRequest request) {
        String res = httpClient.post(headers, Constants.OPEN_FILE_DELETE, request);
        return JSON.parseObject(res, DeleteResponse.class);
    }
}
